package org.dev4j.jdbc.custom;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import javax.sql.DataSource;

import net.vidageek.mirror.Mirror;

import org.apache.log4j.Logger;
import org.dev4j.jdbc.custom.annotations.JdbcColumn;
import org.dev4j.jdbc.custom.annotations.JdbcEntity;
import org.dev4j.jdbc.custom.annotations.enuns.FetchMode;
import org.dev4j.jdbc.custom.interfaces.JdbcCustomTemplate;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

/**
 * JdbcCustomTemplate implementation
 * 
 * @author bruno.fuster
 * 
 */

public class JdbcCustomTemplateImpl extends JdbcTemplate implements JdbcCustomTemplate {

	private static final Logger logger = Logger.getLogger( JdbcCustomTemplateImpl.class );

	public JdbcCustomTemplateImpl( DataSource dataSource ) {

		super( dataSource );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.com.queirozgalvao.equipamentos.jdbc.interfaces.JdbcCustomTemplate#
	 * query(java.lang.Class, java.lang.Object[], java.lang.String)
	 */
	@SuppressWarnings( "unchecked" )
	public List query( Class clazz, Object[] args, String sql ) throws DataAccessException {

		return query( sql, args, createMapper( clazz ) );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.com.queirozgalvao.equipamentos.jdbc.interfaces.JdbcCustomTemplate#
	 * query(java.lang.Class, java.lang.String)
	 */
	@SuppressWarnings( "unchecked" )
	public List query( Class clazz, String sql ) throws DataAccessException {

		return query( sql, createMapper( clazz ) );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.com.queirozgalvao.equipamentos.jdbc.interfaces.JdbcCustomTemplate#
	 * queryForObject(java.lang.Class, java.lang.Object[], java.lang.String)
	 */
	@SuppressWarnings( "unchecked" )
	public Object unique( Class requiredType, Object[] args, String sql ) throws DataAccessException {

		return queryForObject( sql, args, createMapper( requiredType ) );

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.com.queirozgalvao.equipamentos.jdbc.interfaces.JdbcCustomTemplate#
	 * queryForObject(java.lang.Class, java.lang.String)
	 */
	@SuppressWarnings( "unchecked" )
	public Object unique( Class requiredType, String sql ) throws DataAccessException {

		return queryForObject( sql, createMapper( requiredType ) );

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.dev4j.jdbc.custom.interfaces.JdbcCustomTemplate#extract(java.lang
	 * .Class)
	 */
	@SuppressWarnings( "unchecked" )
	public RowMapper extract( Class clazz ) {

		return createMapper( clazz );
	}

	/**
	 * Private implementation for rowMapper creation
	 * 
	 * @param clazz
	 * @return
	 */
	@SuppressWarnings( "unchecked" )
	private RowMapper createMapper( Class clazz ) {

		final Class currentClazz = clazz;

		RowMapper mapper = new RowMapper() {

			public Object mapRow( ResultSet rs, int rownum ) throws SQLException {

				return createMapperForObject( currentClazz, rs );
			}

		};

		return mapper;

	}

	/**
	 * Private implementation for object mapper creation
	 * 
	 * @param {@link Class}
	 * @param {@link ResultSet}
	 * @return {@link Object}
	 * @throws {@link SQLException}
	 */
	@SuppressWarnings( "unchecked" )
	private Object createMapperForObject( Class currentClazz, ResultSet rs ) throws SQLException {

		Object currentObj = null;

		try {
			currentObj = currentClazz.newInstance();
		} catch (IllegalAccessException ex) {
			throw new RuntimeException( ex );
		} catch (InstantiationException ex) {
			throw new RuntimeException( ex );
		}

		List< Field > fields = Mirror.on( currentClazz ).reflectAll().fields();

		for ( Field field : fields ) {

			if ( field.isAnnotationPresent( JdbcColumn.class ) ) {

				String fieldName = field.getAnnotation( JdbcColumn.class ).name();
				Object currentValue = null;

				if ( field.getType().isAnnotationPresent( JdbcEntity.class ) ) {

					if ( field.getAnnotation( JdbcColumn.class ).fetch().equals( FetchMode.JOIN ) ) {
						Object childObj = createMapperForObject( field.getType(), rs );
						Mirror.on( currentObj ).set().field( field ).withValue( childObj );
					}

				} else {

					try {

						// common types
						if ( field.getType().equals( String.class ) ) {
							currentValue = rs.getString( fieldName );
						} else if ( field.getType().equals( Integer.class ) ) {
							currentValue = rs.getInt( fieldName );
						} else if ( field.getType().equals( Long.class ) ) {
							currentValue = rs.getLong( fieldName );
						} else if ( field.getType().equals( Double.class ) ) {
							currentValue = rs.getDouble( fieldName );
						} else if ( field.getType().equals( java.util.Date.class ) ) {
							currentValue = rs.getDate( fieldName );
						}

					} catch (Exception ex) {

						logger.error( "Invalid field: [" + field.getAnnotation( JdbcColumn.class ).name().toString() + "]" );
						throw new RuntimeException( ex );

					}

				}

				if ( currentValue != null ) {
					Mirror.on( currentObj ).set().field( field ).withValue( currentValue );
				}

			}

		}

		return currentObj;
	}
}
